package cn.trigram.mgb;

import cn.trigram.mgb.ext.AbstractTableGenerateCustomize;
import cn.trigram.mgb.mixed_simple_dynamic.MixedMyBatis3DynamicSqlContextCustomize;
import cn.trigram.mgb.mixed_simple_dynamic.MixedMybatis3SimpleContextCustomize;
import cn.trigram.mgb.simple.Mybatis3SimpleContextCustomize;
import java.io.InputStream;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import javax.sql.DataSource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.api.ProgressCallback;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.Context;
import org.mybatis.generator.config.TableConfiguration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;

@Slf4j
public class MybatisGeneratorBuilder {

  private static class LogProgressCallback implements ProgressCallback {

    @Override
    public void introspectionStarted(int i) {

      log.debug(String.format("introspectionStarted %s", i));
    }

    @Override
    public void generationStarted(int i) {

      log.debug(String.format("generationStarted %s", i));

    }

    @Override
    public void saveStarted(int i) {

      log.debug(String.format("saveStarted %s", i));

    }

    @Override
    public void startTask(String s) {

      log.debug(String.format("startTask %s", s));

    }

    @Override
    public void done() {

      log.debug("done");

    }

    @Override
    public void checkCancel() throws InterruptedException {

      log.debug("checkCancel");
    }

  }


  protected abstract static class Builder {

    protected MybatisGeneratorProperties mybatisGeneratorProperties;

    private final InputStream configFileStream;

    protected Configuration configuration;

    protected final List<String> tableNames = new ArrayList<>();

    public Builder(String configFileName) {

      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
      this.configFileStream = classLoader.getResourceAsStream(configFileName);
      init();
    }

    public Builder(String configFileName, String extPropFileName) {

      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
      this.configFileStream = classLoader.getResourceAsStream(configFileName);

      Properties extraProperties = new Properties();
      try {
        extraProperties.load(classLoader.getResourceAsStream(extPropFileName));
        String targetProject = Paths.get(Thread.currentThread().getContextClassLoader().getResource("").toURI())
            .resolve("../../src/test/java").normalize().toString();
        if (!extraProperties.containsKey("javaModelGenerator.targetProject")) {
          extraProperties.setProperty(
              "javaModelGenerator.targetProject", targetProject);
        }
        if (!extraProperties.containsKey("sqlMapGenerator.targetProject")) {
          extraProperties.setProperty(
              "sqlMapGenerator.targetProject", targetProject);
        }
        if (!extraProperties.containsKey("javaClientGenerator.targetProject")) {
          extraProperties.setProperty(
              "javaClientGenerator.targetProject", targetProject);
        }
      } catch (Exception e) {
        log.error(String.format("扩展properties文件[%s]无法加载", extPropFileName), e);
      }
      this.mybatisGeneratorProperties = new MybatisGeneratorProperties(extraProperties);
      init();
    }

    @SneakyThrows
    private void init() {

      List<String>        warnings = new ArrayList<>();
      ConfigurationParser cp       = new ConfigurationParser(this.mybatisGeneratorProperties.getProperties(), warnings);
      configuration = cp.parseConfiguration(this.configFileStream);
      log.debug(String.join(System.lineSeparator(), warnings));
      warnings.clear();
      warnings = null;
    }

    private void handleTableConfiguration(Context context) {

      for (String tableName : this.tableNames) {
        TableConfiguration tableConfiguration = new TableConfiguration(context);
        final String[]     tableInfos         = tableName.split("\\.");

        tableConfiguration.setSchema(tableInfos.length == 1 ? null : tableInfos[0]);
        tableConfiguration.setTableName(tableInfos.length == 1 ? tableInfos[0] : tableInfos[1]);
        context.addTableConfiguration(tableConfiguration);
        /*只生成实体类*/
        AbstractTableGenerateCustomize tableGenerateCustomize = ThreadLocalUtil.getTableGenerateCustomize(context);
        tableGenerateCustomize.setTableConfiguration(tableConfiguration);
        tableGenerateCustomize.setContext(context);
        tableGenerateCustomize.configDefaultSettings();
        tableGenerateCustomize.configProperty();
        tableGenerateCustomize.customizeColumn();
        tableConfiguration.setDomainObjectName(tableGenerateCustomize.getDomainObjectName());
        tableConfiguration.setMapperName(tableGenerateCustomize.getMapperName());
      }
    }

    private void prepareTables(DataSource dataSource, Context context) {

      this.tableNames.clear();
      final AbstractTableGenerateCustomize tableGenerateCustomize = ThreadLocalUtil.getTableGenerateCustomize(context);
      List<String>                   databaseTables         = tableGenerateCustomize.getDatabaseTables();
      if (Objects.nonNull(databaseTables)) {
        tableNames.addAll(databaseTables);
        return;
      }
      try (
          Connection connection = dataSource.getConnection()
      ) {
        DatabaseMetaData databaseMetaData = connection.getMetaData();
        ResultSet        tablesMetaData   = tableGenerateCustomize.getTables(databaseMetaData);
        while (tablesMetaData.next()) {
          String tableSchema = tablesMetaData.getString("TABLE_SCHEM");
          String tableName   = tablesMetaData.getString("TABLE_NAME");
          tableNames.add(StringUtils.defaultString(tableSchema, "") + "." + tableName);
        }
        System.out.println(tableNames);
      } catch (SQLException ignored) {
      }
    }

    private void logWarnings(List<String> warnings) {

      if (!warnings.isEmpty()) {
        System.out.println(String.join(System.lineSeparator(), warnings));
        warnings.clear();
      }
    }

    @SneakyThrows
    public void generate() {

      DataSource    dataSource = this.mybatisGeneratorProperties.getDataSource();
      List<Context> contexts   = this.configuration.getContexts();
      for (Context context : contexts) {
        prepareTables(dataSource, context);
        handleTableConfiguration(context);
        ThreadLocalUtil.getContextCustomize(context).customize(context);
      }

      List<String>         warnings         = new ArrayList<>();
      DefaultShellCallback callback         = new DefaultShellCallback(true);
      MyBatisGenerator     myBatisGenerator = new MyBatisGenerator(this.configuration, callback, warnings);
      myBatisGenerator.generate(new LogProgressCallback());
      logWarnings(warnings);
    }

  }

  public static class Mybatis3SimpleBuilder extends Builder {

    public Mybatis3SimpleBuilder(String configFileName) {

      super(configFileName);
    }

    public Mybatis3SimpleBuilder(String configFileName, String extPropFileName) {

      super(configFileName, extPropFileName);
    }

    public Mybatis3SimpleBuilder addTableGenerateCustomizeForMybatis3Simple(AbstractTableGenerateCustomize customize) {

      configuration.getContexts().stream().filter(c -> StringUtils.equals(c.getId(), ContextIdConstants.simple))
          .findFirst().ifPresent(context -> {
            ThreadLocalUtil.addTableGenerateCustomize(context, customize);
          });
      return this;
    }

    @Override
    public void generate() {

      configuration.getContexts().stream().filter(c -> StringUtils.equals(c.getId(), ContextIdConstants.simple))
          .findFirst().ifPresent(context -> {
            ThreadLocalUtil.addContextCustomize(
                context,
                new Mybatis3SimpleContextCustomize(this.mybatisGeneratorProperties)
            );
          });
      super.generate();
    }

  }

  public static class MixedMybatis3SimpleAndDynamicBuilder extends Builder {

    public MixedMybatis3SimpleAndDynamicBuilder(String configFileName) {

      super(configFileName);
    }

    public MixedMybatis3SimpleAndDynamicBuilder(String configFileName, String extPropFileName) {

      super(configFileName, extPropFileName);
    }

    public MixedMybatis3SimpleAndDynamicBuilder addTableGenerateCustomizeForMybatis3DynamicSql(
        AbstractTableGenerateCustomize customize
    ) {

      configuration.getContexts().stream().filter(c -> StringUtils.equals(c.getId(), ContextIdConstants.mixedDynamic))
          .findFirst().ifPresent(context -> {
            ThreadLocalUtil.addTableGenerateCustomize(context, customize);
          });
      return this;
    }

    public MixedMybatis3SimpleAndDynamicBuilder addTableGenerateCustomizeForMybatis3Simple(
        AbstractTableGenerateCustomize customize
    ) {

      configuration.getContexts().stream().filter(c -> StringUtils.equals(c.getId(), ContextIdConstants.mixedSimple))
          .findFirst().ifPresent(context -> {
            ThreadLocalUtil.addTableGenerateCustomize(context, customize);
          });
      return this;
    }

    @Override
    public void generate() {

      configuration.getContexts().stream().filter(c -> StringUtils.equals(c.getId(), ContextIdConstants.mixedSimple))
          .findFirst().ifPresent(context -> {
            ThreadLocalUtil.addContextCustomize(
                context,
                new MixedMybatis3SimpleContextCustomize(this.mybatisGeneratorProperties)
            );
          });
      configuration.getContexts().stream().filter(c -> StringUtils.equals(c.getId(), ContextIdConstants.mixedDynamic))
          .findFirst().ifPresent(context -> {
            ThreadLocalUtil.addContextCustomize(
                context,
                new MixedMyBatis3DynamicSqlContextCustomize(this.mybatisGeneratorProperties)
            );
          });
      super.generate();
    }

  }

}
